// reflect-metadata
// 1. 安装reflect-metadata  npm i reflect-metadata
// 2. tsconfig设置：
// "experimentalDecorators": true,
// "emitDecoratorMetadata": true,
// 3. 引入 reflect-metadata

// 基本语法
// 定义元数据 2种方式
// 1. 声明式定义：需要放在具体定义的对象上
// @Reflect.metadata(metadataKey, metadataValue);

// 2.命令式定义: 需要给定具体定义的参数
// Reflect.defineMetadata(metadataKey, metadataValue, 定义元数据的对象, propertyKey?)

// 获取元数据
// Reflect.getMetadata(metadataKey, 定义元数据的对象):返回metadataValue

import "reflect-metadata";

/* @Reflect.metadata("classType", "A类-1")
class A{
  prop1: string
  method1() { }
}

// Reflect.defineMetadata("classType", "A类-2", A)

console.log(Reflect.getMetadata("classType", A)); */

// 同样也可以使用工厂模式 --- 声明式
/* const ClassTypeMetaKey = Symbol("classType");

function ClassType(type:string) { 
  return Reflect.metadata(ClassTypeMetaKey, type);
}

@ClassType("A类---声明式工厂")
class A{
  prop1: string
  method1() { }
}

console.log(Reflect.getMetadata(ClassTypeMetaKey, A)); */

// 命令式工厂模式
/* type Constructor<T = any> = new (...args: any[]) => T;

const ClassTypeMetaKey = Symbol("classType");

function ClassType(type:string) { 
  return <T extends Constructor>(target: T) => {
    Reflect.defineMetadata(ClassTypeMetaKey, type, target);
  }
}


class A{
  prop1: string
  method1() { }
}

const metadataCreator = ClassType("A类---命令式工厂");
metadataCreator(A);

console.log(Reflect.getMetadata(ClassTypeMetaKey, A)); */

// 属性和方法的处理
/* class A{
  // @Reflect.metadata("propType1", "prop1-value")
  prop1: string;
  // @Reflect.metadata("propType2", "prop2-value")
  static prop2: string;

  @Reflect.metadata("methodType1","method1-value")
  method1() { }

  @Reflect.metadata("methodType2","method2-value")
  static method2() {}
}

Reflect.defineMetadata("propType1", "prop1-value", A.prototype, "prop1");
Reflect.defineMetadata("propType2", "prop2-value", A, "prop2");

console.log(Reflect.getMetadata("propType1", A.prototype, "prop1"));
console.log(Reflect.getMetadata("propType2", A, "prop2"));

console.log(Reflect.getMetadata("methodType1", A.prototype, "method1"));
console.log(Reflect.getMetadata("methodType2", A, "method2")); */

// 获取装饰器字符串效果
/* const formatMetadataKey = Symbol("format");

function format(formatString: string) {
  return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
  return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

class Greeter {
  @format("Hello, %s")
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    let formatString = getFormat(this, "greeting");
    return formatString.replace("%s", this.greeting);
  }
}

const objG = new Greeter("world");
// console.log(objG.greet());

function greet(obj: any, key: string) {
  let formatString = getFormat(obj, key);
  return formatString.replace("%s", obj[key]);
}

const g = greet(objG, "greeting");
console.log(g); */

// 官网案例
class Point {
  constructor(public x: number, public y: number) {}
}

class User {
  constructor(public x: number, public y: number) {}
}

class Line {
  private _start: Point;
  private _end: Point;

  @validate
  @Reflect.metadata("design:type", Point)
  set start(value: Point) {
    this._start = value;
  }

  get start() {
    return this._start;
  }

  @validate
  @Reflect.metadata("design:type", Point)
  set end(value: Point) {
    this._end = value;
  }

  get end() {
    return this._end;
  }
}

function validate<T>(
  target: any,
  propertyKey: string,
  descriptor: TypedPropertyDescriptor<T>
) {
  let set = descriptor.set!;

  descriptor.set = function (value: T) {
    let type = Reflect.getMetadata("design:type", target, propertyKey);

    if (!(value instanceof type)) {
      throw new TypeError(
        `Invalid type, got ${typeof value} not ${type.name}.`
      );
    }

    set.call(this, value);
  };
}

const line = new Line();
line.start = new User(0, 0); // 这里不是Point类型，会抛出TypeError
